'''
版权所有 (C) [2025] [开发者:2274843805@qq.com]。本基于BS架构的教室上课签到系统（以下简称 “本系统”）遵循
GNU 通用公共许可证版本3（2007年6月发布）的规定，属于自由软件。您有权按照该许可证的条款，对本系统
进行再分发和修改。
本系统在发布时力求具备实用价值，然而不承担任何明示或暗示的担保责任，包括但不限于对适销性、特定用途适用性
的担保，具体内容请查阅GNU通用公共许可证。
您在获取本系统时，应同时收到 GNU 通用公共许可证的副本；若未收到，可前往 GNU 官方网站
（https://www.gnu.org/licenses/gpl-3.0.html）下载。一旦使用本系统，即视为您接受并承诺遵守该许可证的全部条款。

Copyright (C) [Year] [Developer's Name/Team Name]. This BS architecture-based classroom check-in 
system (hereinafter referred to as "the System") is free software licensed under the GNU 
General Public License Version 3, published by the Free Software Foundation in June 2007. 
You are entitled to redistribute and modify the System in accordance with the terms of this License.
The System is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
For more details, please refer to the GNU General Public License.
You should have received a copy of the GNU General Public License when you obtained the System; 
if not, you can download it from the official GNU website (https://www.gnu.org/licenses/gpl-3.0.html). 
By using the System, you are deemed to have accepted and agreed to comply with all the terms of this License. 
'''

from flask import Flask, render_template, redirect, url_for, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user
from flask_socketio import SocketIO, emit, join_room
import os
from datetime import datetime
import io
import codecs

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///cinema.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

# 初始化扩展
db = SQLAlchemy(app)
login_manager = LoginManager(app)
login_manager.login_view = 'login'
socketio = SocketIO(app, cors_allowed_origins="*")

# 数据库模型
class User(UserMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    password = db.Column(db.String(120), nullable=False)
    is_admin = db.Column(db.Boolean, default=False)
    nick = db.Column(db.String(50), nullable=True)
    seat_selections = db.relationship('SeatSelection', backref='user', lazy=True)
    
    def __repr__(self):
        return f'<User {self.username}, nick={self.nick}>'

class SeatLayout(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    row_count = db.Column(db.Integer, nullable=False, default=8)
    column_count = db.Column(db.Integer, nullable=False, default=12)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)

class Seat(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    row = db.Column(db.Integer, nullable=False)
    number = db.Column(db.Integer, nullable=False)
    is_available = db.Column(db.Boolean, default=True)
    is_reserved = db.Column(db.Boolean, default=False)
    layout_id = db.Column(db.Integer, db.ForeignKey('seat_layout.id'), nullable=False)
    selections = db.relationship('SeatSelection', backref='seat', lazy=True)

class SeatSelection(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    seat_id = db.Column(db.Integer, db.ForeignKey('seat.id'), nullable=False)
    selected_at = db.Column(db.DateTime, default=datetime.utcnow)

# Flask-Login 回调
@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))

# 路由
@app.route('/')
def index():
    return redirect(url_for('login'))

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        user = User.query.filter_by(username=username).first()
        if user and password == user.password:  # 注意：实际应用中应使用密码哈希
            login_user(user)
            if user.is_admin:
                return redirect(url_for('admin_dashboard'))
            else:
                return redirect(url_for('user_dashboard'))
    return render_template('login.html')

@app.route('/logout')
@login_required
def logout():
    logout_user()
    return redirect(url_for('login'))

@app.route('/admin/dashboard')
@login_required
def admin_dashboard():
    if not current_user.is_admin:
        return redirect(url_for('user_dashboard'))
    # 检查是否有座位布局，如果没有则创建默认布局
    layout = SeatLayout.query.order_by(SeatLayout.created_at.desc()).first()
    if not layout:
        layout = SeatLayout(row_count=8, column_count=12)
        db.session.add(layout)
        db.session.commit()
        # 创建座位
        for row in range(1, layout.row_count + 1):
            for col in range(1, layout.column_count + 1):
                seat = Seat(row=row, number=col, layout_id=layout.id)
                db.session.add(seat)
        db.session.commit()
    seats = Seat.query.filter_by(layout_id=layout.id).all()
    
    # 获取每个座位的选座信息
    seat_data = []
    for seat in seats:
        # 获取选座人数
        selection_count = SeatSelection.query.filter_by(seat_id=seat.id).count()
        # 获取选择该座位的所有用户
        seat_selections = SeatSelection.query.filter_by(seat_id=seat.id).all()
        user_list = [{'user_id': s.user.id, 'username': s.user.username, 'nick': s.user.nick, 'selected_at': s.selected_at.strftime('%Y-%m-%d %H:%M:%S')} for s in seat_selections]
        
        seat_data.append({
            'id': seat.id,
            'row': seat.row,
            'number': seat.number,
            'is_available': seat.is_available,
            'selection_count': selection_count,
            'user_list': user_list
        })
    
    return render_template('admin_dashboard.html', layout=layout, seat_data=seat_data)

@app.route('/user/dashboard')
@login_required
def user_dashboard():
    if current_user.is_admin:
        return redirect(url_for('admin_dashboard'))
    # 获取最新的座位布局和座位信息
    layout = SeatLayout.query.order_by(SeatLayout.created_at.desc()).first()
    if not layout:
        layout = SeatLayout(row_count=8, column_count=12)
        db.session.add(layout)
        db.session.commit()
        # 创建座位
        for row in range(1, layout.row_count + 1):
            for col in range(1, layout.column_count + 1):
                seat = Seat(row=row, number=col, layout_id=layout.id)
                db.session.add(seat)
        db.session.commit()
    seats = Seat.query.filter_by(layout_id=layout.id).all()
    # 获取当前用户的选座信息
    user_selections = SeatSelection.query.filter_by(user_id=current_user.id).all()
    
    # 为每个座位准备包含用户列表的数据，类似于管理员界面
    seat_data = []
    for seat in seats:
        # 获取选座人数
        selection_count = SeatSelection.query.filter_by(seat_id=seat.id).count()
        # 检查当前用户是否选择了这个座位
        user_selected = SeatSelection.query.filter_by(user_id=current_user.id, seat_id=seat.id).first() is not None
        # 获取选择该座位的所有用户
        seat_selections = SeatSelection.query.filter_by(seat_id=seat.id).all()
        user_list = [{'user_id': s.user.id, 'username': s.user.username, 'nick': s.user.nick, 'selected_at': s.selected_at.strftime('%Y-%m-%d %H:%M:%S')} for s in seat_selections]
        
        seat_data.append({
            'id': seat.id,
            'row': seat.row,
            'number': seat.number,
            'is_available': seat.is_available,
            'selection_count': selection_count,
            'user_selected': user_selected,
            'user_list': user_list
        })
    
    return render_template('user_dashboard.html', layout=layout, seats=seats, user_selections=user_selections, seat_data=seat_data)

# WebSocket事件处理
@socketio.on('connect')
def handle_connect():
    if current_user.is_authenticated:
        join_room('cinema_room')

@socketio.on('update_seat_status')
def handle_update_seat_status(data):
    if current_user.is_authenticated and current_user.is_admin:
        seat_id = data['seat_id']
        is_available = data['is_available']
        seat = Seat.query.get(seat_id)
        if seat:
            # 检查座位状态是否由可用转为不可用
            if seat.is_available and not is_available:
                # 获取该座位的所有选座记录
                seat_selections = SeatSelection.query.filter_by(seat_id=seat_id).all()
                if seat_selections:
                    # 清除所有选座记录
                    for selection in seat_selections:
                        db.session.delete(selection)
                    db.session.commit()
                    
                    # 广播选座清除信息给所有客户端
                    emit('seat_deselected', {
                        'seat_id': seat_id,
                        'user_id': current_user.id,
                        'selection_count': 0,
                        'username': current_user.username,
                        'nick': current_user.nick,
                        'user_list': []
                    }, room='cinema_room')
            
            # 更新座位状态
            seat.is_available = is_available
            db.session.commit()
            
            # 广播座位状态更新
            emit('seat_status_updated', {
                'seat_id': seat_id,
                'is_available': is_available
            }, room='cinema_room')

@socketio.on('select_seat')
def handle_select_seat(data):
    if current_user.is_authenticated and not current_user.is_admin:
        seat_id = data['seat_id']
        seat = Seat.query.get(seat_id)
        if seat and seat.is_available:
            # 检查用户是否已经选择了这个座位
            existing_selection = SeatSelection.query.filter_by(user_id=current_user.id, seat_id=seat_id).first()
            if not existing_selection:
                # 单座选择限制：删除用户对其他所有座位的选择
                user_other_selections = SeatSelection.query.filter_by(user_id=current_user.id).all()
                for other_selection in user_other_selections:
                    if other_selection.seat_id != seat_id:
                        # 获取需要更新的座位ID
                        other_seat_id = other_selection.seat_id
                        
                        # 删除用户对其他座位的选择
                        db.session.delete(other_selection)
                        db.session.commit()
                        
                        # 检查有多少人选择了这个被取消的座位
                        deselection_count = SeatSelection.query.filter_by(seat_id=other_seat_id).count()
                        
                        # 获取剩余选择该被取消座位的所有用户
                        remaining_selections = SeatSelection.query.filter_by(seat_id=other_seat_id).all()
                        remaining_user_list = [{'user_id': s.user.id, 'username': s.user.username, 'nick': s.user.nick, 'selected_at': s.selected_at.strftime('%Y-%m-%d %H:%M:%S')} for s in remaining_selections]
                        
                        # 广播座位取消选择更新
                        emit('seat_deselected', {
                            'seat_id': other_seat_id,
                            'user_id': current_user.id,
                            'selection_count': deselection_count,
                            'username': current_user.username,
                            'nick': current_user.nick,
                            'user_list': remaining_user_list
                        }, room='cinema_room')
                
                # 创建新的选座记录
                selection = SeatSelection(user_id=current_user.id, seat_id=seat_id)
                db.session.add(selection)
                db.session.commit()
                
                # 检查有多少人选择了这个座位
                selection_count = SeatSelection.query.filter_by(seat_id=seat_id).count()
                
                # 获取选择该座位的所有用户
                all_selections = SeatSelection.query.filter_by(seat_id=seat_id).all()
                user_list = [{'user_id': s.user.id, 'username': s.user.username, 'nick': s.user.nick, 'selected_at': s.selected_at.strftime('%Y-%m-%d %H:%M:%S')} for s in all_selections]
                
                # 广播更新
                emit('seat_selected', {
                    'seat_id': seat_id,
                    'user_id': current_user.id,
                    'selection_count': selection_count,
                    'username': current_user.username,
                    'nick': current_user.nick,
                    'user_list': user_list
                }, room='cinema_room')

@socketio.on('deselect_seat')
def handle_deselect_seat(data):
    if current_user.is_authenticated and not current_user.is_admin:
        seat_id = data['seat_id']
        # 删除用户对这个座位的选择
        selection = SeatSelection.query.filter_by(user_id=current_user.id, seat_id=seat_id).first()
        if selection:
            db.session.delete(selection)
            db.session.commit()
            
            # 检查有多少人选择了这个座位
            selection_count = SeatSelection.query.filter_by(seat_id=seat_id).count()
            
            # 获取剩余选择该座位的所有用户
            remaining_selections = SeatSelection.query.filter_by(seat_id=seat_id).all()
            user_list = [{'user_id': s.user.id, 'username': s.user.username, 'nick': s.user.nick, 'selected_at': s.selected_at.strftime('%Y-%m-%d %H:%M:%S')} for s in remaining_selections]
            
            # 广播更新
            emit('seat_deselected', {
                'seat_id': seat_id,
                'user_id': current_user.id,
                'selection_count': selection_count,
                'username': current_user.username,
                'nick': current_user.nick,
                'user_list': user_list
            }, room='cinema_room')

# API接口
@app.route('/api/clear_all_selections', methods=['POST'])
@login_required
def clear_all_selections():
    if not current_user.is_admin:
        return jsonify({'status': 'error', 'message': 'Unauthorized'})
    
    # 获取所有一般用户的选座记录
    regular_users = User.query.filter_by(is_admin=False).all()
    regular_user_ids = [user.id for user in regular_users]
    
    # 删除所有一般用户的选座记录
    seat_selections = SeatSelection.query.filter(SeatSelection.user_id.in_(regular_user_ids)).all()
    
    # 为了广播更新，我们需要按座位分组记录
    seat_updates = {}
    for selection in seat_selections:
        if selection.seat_id not in seat_updates:
            seat_updates[selection.seat_id] = set()
        seat_updates[selection.seat_id].add(selection.user_id)
    
    # 删除所有选座记录
    for selection in seat_selections:
        db.session.delete(selection)
    db.session.commit()
    
    # 对每个受影响的座位广播更新
    for seat_id, user_ids in seat_updates.items():
        # 检查当前座位还有多少人选择
        remaining_count = SeatSelection.query.filter_by(seat_id=seat_id).count()
        
        # 获取剩余选择该座位的所有用户
        remaining_selections = SeatSelection.query.filter_by(seat_id=seat_id).all()
        remaining_user_list = [{'user_id': s.user.id, 'username': s.user.username, 'nick': s.user.nick, 'selected_at': s.selected_at.strftime('%Y-%m-%d %H:%M:%S')} for s in remaining_selections]
        
        # 广播座位状态更新
        socketio.emit('seat_deselected', {
            'seat_id': seat_id,
            'user_id': current_user.id,
            'selection_count': remaining_count,
            'username': current_user.username,
            'nick': current_user.nick,
            'user_list': remaining_user_list
        }, room='cinema_room')
    
    return jsonify({'status': 'success', 'message': '所有一般用户的选座数据已清空'})

@app.route('/api/export_selections')
@login_required
def export_selections():
    if not current_user.is_admin:
        return jsonify({'status': 'error', 'message': '权限不足'}), 403
    
    try:
        # 获取所有非管理员用户，并按用户名排序
        users = User.query.filter_by(is_admin=False).order_by(User.username).all()
        
        user_selections = []
        for user in users:
            # 检查用户是否有选座
            seat_selection = SeatSelection.query.filter_by(user_id=user.id).first()
            seat_info = None
            other_selectors = []
            
            if seat_selection:
                # 获取座位信息
                seat = Seat.query.get(seat_selection.seat_id)
                if seat:
                    seat_info = {
                        'row': seat.row,
                        'number': seat.number
                    }
                    
                    # 获取该座位的所有选座人员，排除当前用户
                    all_selections = SeatSelection.query.filter_by(seat_id=seat.id).all()
                    if len(all_selections) > 1:
                        for selection in all_selections:
                            if selection.user_id != user.id:
                                other_user = User.query.get(selection.user_id)
                                if other_user:
                                    other_selectors.append({
                                        'username': other_user.username,
                                        'nick': other_user.nick
                                    })
            
            user_selections.append({
                'username': user.username,
                'nick': user.nick,
                'seat': seat_info,
                'other_selectors': other_selectors
            })
        
        return jsonify({
            'status': 'success',
            'user_selections': user_selections
        })
    except Exception as e:
        return jsonify({'status': 'error', 'message': str(e)}), 500

@app.route('/api/download_attendance')
@login_required
def download_attendance():
    if not current_user.is_admin:
        return jsonify({'status': 'error', 'message': '权限不足'}), 403
    
    try:
        # 获取所有非管理员用户，并按用户名排序
        users = User.query.filter_by(is_admin=False).order_by(User.username).all()
        
        # 创建内存文件流，使用GBK编码
        output = io.StringIO()
        
        # 遍历用户生成CSV内容
        for user in users:
            # 检查用户是否有选座
            seat_selection = SeatSelection.query.filter_by(user_id=user.id).first()
            seat_info = '无'
            other_users_str = ''
            
            if seat_selection:
                # 获取座位信息
                seat = Seat.query.get(seat_selection.seat_id)
                if seat:
                    seat_info = f'第{seat.row}行{seat.number}列'
                    
                    # 获取该座位的所有选座人员，排除当前用户
                    all_selections = SeatSelection.query.filter_by(seat_id=seat.id).all()
                    if len(all_selections) > 1:
                        other_users_list = []
                        for selection in all_selections:
                            if selection.user_id != user.id:
                                other_user = User.query.get(selection.user_id)
                                if other_user:
                                    other_users_list.append(f'{other_user.username}({other_user.nick or other_user.username})')
                        if other_users_list:
                            other_users_str = f',其他选座人:{"; ".join(other_users_list)}'
            
            # 写入用户信息，格式：用户名,昵称,座位信息[其他选座人信息]
            line = f'{user.username},{user.nick or user.username},{seat_info}{other_users_str}\n'
            output.write(line)
        
        # 获取字符串内容并转换为GBK编码
        content = output.getvalue()
        output.close()
        
        # 创建响应对象，设置正确的头信息
        response = app.response_class(
            response=codecs.encode(content, 'gbk'),
            status=200,
            mimetype='text/csv'
        )
        response.headers['Content-Disposition'] = 'attachment; filename=attendance.csv'
        response.headers['Content-Type'] = 'text/csv; charset=gbk'
        
        return response
    except Exception as e:
        return jsonify({'status': 'error', 'message': str(e)}), 500

@app.route('/api/seats', methods=['GET'])
@login_required
def get_seats():
    layout = SeatLayout.query.order_by(SeatLayout.created_at.desc()).first()
    if not layout:
        return jsonify([])
    seats = Seat.query.filter_by(layout_id=layout.id).all()
    seat_data = []
    for seat in seats:
        # 获取选座人数
        selection_count = SeatSelection.query.filter_by(seat_id=seat.id).count()
        # 检查当前用户是否选择了这个座位
        user_selected = SeatSelection.query.filter_by(user_id=current_user.id, seat_id=seat.id).first() is not None
        seat_data.append({
            'id': seat.id,
            'row': seat.row,
            'number': seat.number,
            'is_available': seat.is_available,
            'selection_count': selection_count,
            'user_selected': user_selected
        })
    return jsonify(seat_data)

@app.route('/api/seat/<int:seat_id>/info', methods=['GET'])
@login_required
def get_seat_info(seat_id):
    # 检查是否是管理员或该座位有当前用户的选择
    if not current_user.is_admin:
        # 普通用户只能查看自己选择的座位信息
        user_selected = SeatSelection.query.filter_by(user_id=current_user.id, seat_id=seat_id).first() is not None
        if not user_selected:
            return jsonify({'status': 'error', 'message': 'Unauthorized'}), 403
    
    # 获取座位信息
    seat = Seat.query.get(seat_id)
    if not seat:
        return jsonify({'status': 'error', 'message': 'Seat not found'}), 404
    
    # 获取选座人数
    selection_count = SeatSelection.query.filter_by(seat_id=seat_id).count()
    
    # 获取选择该座位的所有用户
    seat_selections = SeatSelection.query.filter_by(seat_id=seat_id).all()
    user_list = [{'user_id': s.user.id, 'username': s.user.username, 'nick': s.user.nick, 'selected_at': s.selected_at.strftime('%Y-%m-%d %H:%M:%S')} for s in seat_selections]
    
    return jsonify({
        'id': seat.id,
        'row': seat.row,
        'number': seat.number,
        'is_available': seat.is_available,
        'selection_count': selection_count,
        'user_list': user_list
    })

@app.route('/api/layout/update', methods=['POST'])
@login_required
def update_layout():
    if not current_user.is_admin:
        return jsonify({'status': 'error', 'message': 'Unauthorized'})
    data = request.json
    row_count = data.get('row_count', 8)
    column_count = data.get('column_count', 12)
    
    # 创建新的布局
    new_layout = SeatLayout(row_count=row_count, column_count=column_count)
    db.session.add(new_layout)
    db.session.commit()
    
    # 创建新布局的座位
    for row in range(1, row_count + 1):
        for col in range(1, column_count + 1):
            seat = Seat(row=row, number=col, layout_id=new_layout.id)
            db.session.add(seat)
    db.session.commit()
    
    # 广播布局更新
    socketio.emit('layout_updated', {
        'row_count': row_count,
        'column_count': column_count
    }, room='cinema_room')
    
    return jsonify({'status': 'success'})

@app.route('/api/init_database', methods=['POST'])
@login_required
def init_database():
    if not current_user.is_admin:
        return jsonify({'status': 'error', 'message': 'Unauthorized'})
    
    try:
        with app.app_context():
            # 清空所有数据表
            db.drop_all()
            db.create_all()
            
            # 调用init_db函数重新初始化数据库
            init_db()
            
            return jsonify({'status': 'success', 'message': '数据库初始化成功！'})
    except Exception as e:
        return jsonify({'status': 'error', 'message': f'初始化数据库失败：{str(e)}'})

# 初始化数据库
def init_db():
    with app.app_context():
        db.create_all()
        
        # 从文件加载管理员用户
        admin_file_path = os.path.join(os.path.dirname(__file__), 'admin_list.txt')
        if os.path.exists(admin_file_path):
            with open(admin_file_path, 'r', encoding='utf-8') as f:
                for line in f:
                    line = line.strip()
                    if line and not line.startswith('#'):
                        parts = line.split()
                        if len(parts) >= 2:
                            username = parts[0]
                            # 如果有昵称，那么密码是第2个部分，昵称是剩余部分
                            if len(parts) >= 3:
                                password = parts[1]
                                nick = ' '.join(parts[2:])
                            else:
                                # 没有昵称，密码是剩余部分
                                password = ' '.join(parts[1:])
                                nick = None
                            
                            # 检查用户是否已存在
                            existing_user = User.query.filter_by(username=username).first()
                            if existing_user:
                                # 更新现有用户信息
                                existing_user.password = password
                                existing_user.is_admin = True
                                if nick:
                                    existing_user.nick = nick
                            else:
                                # 创建新的管理员用户
                                admin_user = User(username=username, password=password, is_admin=True, nick=nick)
                                db.session.add(admin_user)
        
        # 从文件加载普通用户
        user_file_path = os.path.join(os.path.dirname(__file__), 'user_list.txt')
        if os.path.exists(user_file_path):
            with open(user_file_path, 'r', encoding='utf-8') as f:
                for line in f:
                    line = line.strip()
                    if line and not line.startswith('#'):
                        parts = line.split()
                        if len(parts) >= 2:
                            username = parts[0]
                            # 如果有昵称，那么密码是第2个部分，昵称是剩余部分
                            if len(parts) >= 3:
                                password = parts[1]
                                nick = ' '.join(parts[2:])
                            else:
                                # 没有昵称，密码是剩余部分
                                password = ' '.join(parts[1:])
                                nick = None
                            
                            # 检查用户是否已存在
                            existing_user = User.query.filter_by(username=username).first()
                            if existing_user:
                                # 更新现有用户信息
                                existing_user.password = password
                                existing_user.is_admin = False
                                if nick:
                                    existing_user.nick = nick
                            else:
                                # 创建新的普通用户
                                regular_user = User(username=username, password=password, is_admin=False, nick=nick)
                                db.session.add(regular_user)
        
        db.session.commit()

import sys
import argparse

if __name__ == '__main__':
    # 解析命令行参数
    parser = argparse.ArgumentParser(description='Cinema Seat Booking System')
    parser.add_argument('--port', type=int, default=5000, help='Server port (default: 5000)')
    args = parser.parse_args()
    
    # 初始化数据库
    init_db()
    # 运行应用，使用指定端口
    socketio.run(app, debug=True, host='0.0.0.0', port=args.port)